﻿#include "StdAfx.h"
#include "UIManager.h"

#include <zmouse.h>

/////////////////////////////////////////////////////////////////////////////////////
//
static UINT GetNameHash(LPCTSTR pstrName) {
	UINT i = 0;
	SIZE_T len = _tcslen(pstrName);
	while (len-- > 0) {
		i = (i << 5) + i + pstrName[len];
	}
	return i;
}

static UINT MapKeyState() {
	UINT uState = 0;
	if (::GetKeyState(VK_CONTROL) < 0) {
		uState |= MK_CONTROL;
	}
	if (::GetKeyState(VK_RBUTTON) < 0) {
		uState |= MK_LBUTTON;
	}
	if (::GetKeyState(VK_LBUTTON) < 0) {
		uState |= MK_RBUTTON;
	}
	if (::GetKeyState(VK_SHIFT) < 0)  {
		uState |= MK_SHIFT;
	}
	if (::GetKeyState(VK_MENU) < 0) {
		uState |= MK_ALT;
	}
	return uState;
}

/////////////////////////////////////////////////////////////////////////////////////
//
#define IDB_ICONS16 200
#define IDB_ICONS24 201
#define IDB_ICONS32 202
#define IDB_ICONS50 203

/////////////////////////////////////////////////////////////////////////////////////
//
HPEN m_hPens[UICOLOR__LAST] = { 0 };
HFONT m_hFonts[UIFONT__LAST] = { 0 };
HBRUSH m_hBrushes[UICOLOR__LAST] = { 0 };
LOGFONT m_aLogFonts[UIFONT__LAST] = { 0 };
COLORREF m_clrColors[UICOLOR__LAST][2] = { 0 };
TEXTMETRIC m_aTextMetrics[UIFONT__LAST] = { 0 };
HIMAGELIST m_himgIcons16 = NULL;
HIMAGELIST m_himgIcons24 = NULL;
HIMAGELIST m_himgIcons32 = NULL;
HIMAGELIST m_himgIcons50 = NULL;

/////////////////////////////////////////////////////////////////////////////////////
//
HINSTANCE CPaintManagerUI::m_hInstance = NULL;
HINSTANCE CPaintManagerUI::m_hLangInst = NULL;
CStdPtrArray CPaintManagerUI::m_aPreMessages;

CPaintManagerUI::CPaintManagerUI() :
	m_hWndPaint(NULL),
	m_hDcPaint(NULL),
	m_hDcOffscreen(NULL),
	m_hbmpOffscreen(NULL),
	m_hwndTooltip(NULL),
	m_pRoot(NULL),
	m_pFocus(NULL),
	m_pEventHover(NULL),
	m_pEventClick(NULL),
	m_pEventKey(NULL),
	m_bFocusNeeded(false),
	m_bResizeNeeded(false),
	m_bMouseTracking(false),
	m_bOffscreenPaint(true),
	m_aPostPaint(sizeof(TPostPaintUI)) {
	if ( m_hFonts[1] == NULL ) {
		// Fill in default font information
		LOGFONT lf = { 0 };
		::GetObject(::GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT), &lf);
		_tcscpy(lf.lfFaceName, _T("Tahoma"));
		// TODO: Handle "large fonts" or other font sizes when
		//       screen resolution changes!!!
		lf.lfHeight = -12;
		m_aLogFonts[UIFONT_NORMAL] = lf;
		m_aLogFonts[UIFONT_CAPTION] = lf;
		LOGFONT lfBold = lf;
		lfBold.lfWeight += FW_BOLD;
		m_aLogFonts[UIFONT_BOLD] = lfBold;
		lfBold.lfHeight -= 2;
		m_aLogFonts[UIFONT_TITLE] = lfBold;
		lfBold.lfHeight -= 4;
		m_aLogFonts[UIFONT_HEADLINE] = lfBold;
		LOGFONT lfSubscript = lf;
		lfSubscript.lfHeight -= 4;
		m_aLogFonts[UIFONT_SUBSCRIPT] = lfSubscript;
		LOGFONT lfLink = lf;
		lfLink.lfUnderline = TRUE;
		m_aLogFonts[UIFONT_LINK] = lfLink;

		// Fill the color table
		m_clrColors[UICOLOR_WINDOW_BACKGROUND][0]            = RGB(239,239,235);
		m_clrColors[UICOLOR_DIALOG_BACKGROUND][0]            = RGB(238,0,0);
		m_clrColors[UICOLOR_DIALOG_TEXT_NORMAL][0]           = RGB(0,0,0);
		m_clrColors[UICOLOR_DIALOG_TEXT_DARK][0]             = RGB(96,96,80);
		m_clrColors[UICOLOR_TITLE_BACKGROUND][0]             = RGB(114,136,172);
		m_clrColors[UICOLOR_TITLE_TEXT][0]                   = RGB(255,255,255);
		m_clrColors[UICOLOR_TITLE_BORDER_LIGHT][0]           = RGB(171,192,231);
		m_clrColors[UICOLOR_TITLE_BORDER_DARK][0]            = RGB(0,55,122);
		//m_clrColors[UICOLOR_BUTTON_BACKGROUND_NORMAL][0]     = RGB(250,250,252);
		//m_clrColors[UICOLOR_BUTTON_BACKGROUND_NORMAL][1]    = RGB(215,215,227);
		m_clrColors[UICOLOR_BUTTON_BACKGROUND_NORMAL][0] = RGB(0, 0, 255); // 按钮的起始渐变色
		m_clrColors[UICOLOR_BUTTON_BACKGROUND_NORMAL][1] = RGB(215, 215, 0); // 按钮的结束渐变色
		m_clrColors[UICOLOR_BUTTON_BACKGROUND_DISABLED][0]   = RGB(248,248,248);
		m_clrColors[UICOLOR_BUTTON_BACKGROUND_DISABLED][1]  = RGB(214,214,214);
		m_clrColors[UICOLOR_BUTTON_BACKGROUND_PUSHED][0]     = RGB(215,215,227);
		m_clrColors[UICOLOR_BUTTON_BACKGROUND_PUSHED][1]    = RGB(250,250,252);
		m_clrColors[UICOLOR_BUTTON_TEXT_NORMAL][0]           = RGB(200,0,0); // 按钮的文本颜色
		m_clrColors[UICOLOR_BUTTON_TEXT_PUSHED][0]           = RGB(0,0,20);
		m_clrColors[UICOLOR_BUTTON_TEXT_DISABLED][0]         = RGB(204,204,204);
		//m_clrColors[UICOLOR_BUTTON_BORDER_LIGHT][0]          = RGB(123,158,189);
		//m_clrColors[UICOLOR_BUTTON_BORDER_DARK][0]           = RGB(123,158,189);
		m_clrColors[UICOLOR_BUTTON_BORDER_LIGHT][0] = RGB(0, 255, 0); // 按钮的边框颜色
		m_clrColors[UICOLOR_BUTTON_BORDER_DARK][0] = RGB(0, 255, 0); // 按钮的边框颜色
		m_clrColors[UICOLOR_BUTTON_BORDER_DISABLED][0]       = RGB(204,204,204);
		m_clrColors[UICOLOR_BUTTON_BORDER_FOCUS][0]          = RGB(140,140,140);
		m_clrColors[UICOLOR_TOOL_BACKGROUND_NORMAL][0]       = RGB(114,136,172);
		m_clrColors[UICOLOR_TOOL_BACKGROUND_DISABLED][0]     = RGB(100,121,156);
		m_clrColors[UICOLOR_TOOL_BACKGROUND_HOVER][0]        = RGB(100,121,156);
		m_clrColors[UICOLOR_TOOL_BACKGROUND_PUSHED][0]       = RGB(80,101,136);
		m_clrColors[UICOLOR_TOOL_BORDER_NORMAL][0]           = RGB(0,55,122);
		m_clrColors[UICOLOR_TOOL_BORDER_DISABLED][0]         = RGB(0,55,122);
		m_clrColors[UICOLOR_TOOL_BORDER_HOVER][0]            = RGB(0,55,122);
		m_clrColors[UICOLOR_TOOL_BORDER_PUSHED][0]           = RGB(0,55,122);
		m_clrColors[UICOLOR_EDIT_BACKGROUND_DISABLED][0]     = RGB(255,251,255);
		m_clrColors[UICOLOR_EDIT_BACKGROUND_READONLY][0]     = RGB(255,251,255);
		m_clrColors[UICOLOR_EDIT_BACKGROUND_NORMAL][0]       = RGB(255,255,255);
		m_clrColors[UICOLOR_EDIT_BACKGROUND_HOVER][0]        = RGB(255,251,255);
		m_clrColors[UICOLOR_EDIT_TEXT_NORMAL][0]             = RGB(255, 0, 200); // 修改默认字体颜色为红色
		m_clrColors[UICOLOR_EDIT_TEXT_DISABLED][0]           = RGB(167,166,170);
		m_clrColors[UICOLOR_EDIT_TEXT_READONLY][0]           = RGB(167,166,170);      
		m_clrColors[UICOLOR_NAVIGATOR_BACKGROUND][0]         = RGB(229,217,213);
		m_clrColors[UICOLOR_NAVIGATOR_BACKGROUND][1]        = RGB(201,199,187);
		m_clrColors[UICOLOR_NAVIGATOR_TEXT_NORMAL][0]        = RGB(102,102,102);
		m_clrColors[UICOLOR_NAVIGATOR_TEXT_SELECTED][0]      = RGB(0,0,0);
		m_clrColors[UICOLOR_NAVIGATOR_TEXT_PUSHED][0]        = RGB(0,0,0);       
		m_clrColors[UICOLOR_NAVIGATOR_BORDER_NORMAL][0]      = RGB(131,133,116);
		m_clrColors[UICOLOR_NAVIGATOR_BORDER_SELECTED][0]    = RGB(159,160,144);
		m_clrColors[UICOLOR_NAVIGATOR_BUTTON_HOVER][0]       = RGB(200,200,200);
		m_clrColors[UICOLOR_NAVIGATOR_BUTTON_PUSHED][0]      = RGB(184,184,183);
		m_clrColors[UICOLOR_NAVIGATOR_BUTTON_SELECTED][0]    = RGB(238,238,238);
		m_clrColors[UICOLOR_TAB_BACKGROUND_NORMAL][0]        = RGB(255,251,255);
		m_clrColors[UICOLOR_TAB_FOLDER_NORMAL][0]            = RGB(255,251,255);
		m_clrColors[UICOLOR_TAB_FOLDER_NORMAL][1]           = RGB(233,231,215);
		m_clrColors[UICOLOR_TAB_FOLDER_SELECTED][0]          = RGB(255,251,255);
		m_clrColors[UICOLOR_TAB_BORDER][0]                   = RGB(148,166,181);
		m_clrColors[UICOLOR_TAB_TEXT_NORMAL][0]              = RGB(0,0,0);
		m_clrColors[UICOLOR_TAB_TEXT_SELECTED][0]            = RGB(0,0,0);
		m_clrColors[UICOLOR_TAB_TEXT_DISABLED][0]            = RGB(0,0,0);      
		m_clrColors[UICOLOR_HEADER_BACKGROUND][0]            = RGB(233,231,215);
		m_clrColors[UICOLOR_HEADER_BACKGROUND][1]           = RGB(150,150,147);
		m_clrColors[UICOLOR_HEADER_BORDER][0]                = RGB(218,219,201);
		m_clrColors[UICOLOR_HEADER_SEPARATOR][0]             = RGB(197,193,177);
		m_clrColors[UICOLOR_HEADER_TEXT][0]                  = RGB(0,0,0);
		m_clrColors[UICOLOR_TASK_BACKGROUND][0]              = RGB(230,243,255);
		m_clrColors[UICOLOR_TASK_BACKGROUND][1]             = RGB(255,255,255);
		m_clrColors[UICOLOR_TASK_BORDER][0]                  = RGB(140,158,198);
		m_clrColors[UICOLOR_TASK_CAPTION][0]                 = RGB(140,158,198);
		m_clrColors[UICOLOR_TASK_TEXT][0]                    = RGB(65,65,110);
		m_clrColors[UICOLOR_LINK_TEXT_NORMAL][0]             = RGB(0,0,255);
		m_clrColors[UICOLOR_LINK_TEXT_HOVER][0]              = RGB(0,0,100);
		m_clrColors[UICOLOR_CONTROL_BACKGROUND_NORMAL][0]    = RGB(255,255,255);
		m_clrColors[UICOLOR_CONTROL_BACKGROUND_SELECTED][0]  = RGB(173,195,231);
		m_clrColors[UICOLOR_CONTROL_BACKGROUND_READONLY][0]  = RGB(255,255,255);
		m_clrColors[UICOLOR_CONTROL_BACKGROUND_DISABLED][0]  = RGB(255,255,255);
		m_clrColors[UICOLOR_CONTROL_BACKGROUND_HOVER][0]     = RGB(233,245,255);
		m_clrColors[UICOLOR_CONTROL_BACKGROUND_SORTED][0]    = RGB(242,242,246);
		m_clrColors[UICOLOR_CONTROL_BACKGROUND_EXPANDED][0]  = RGB(255,255,255);
		m_clrColors[UICOLOR_CONTROL_BACKGROUND_EXPANDED][1] = RGB(236,242,255);
		m_clrColors[UICOLOR_CONTROL_BORDER_NORMAL][0]        = RGB(123,158,189);
		m_clrColors[UICOLOR_CONTROL_BORDER_SELECTED][0]      = RGB(123,158,189);
		m_clrColors[UICOLOR_CONTROL_BORDER_DISABLED][0]      = RGB(204,204,204);
		m_clrColors[UICOLOR_CONTROL_TEXT_NORMAL][0]          = RGB(0,0,0);
		m_clrColors[UICOLOR_CONTROL_TEXT_SELECTED][0]        = RGB(0,0,0);
		m_clrColors[UICOLOR_CONTROL_TEXT_DISABLED][0]        = RGB(204,204,204);
		m_clrColors[UICOLOR_STANDARD_BLACK][0]               = RGB(0,0,0);
		m_clrColors[UICOLOR_STANDARD_YELLOW][0]              = RGB(255,255,204);
		m_clrColors[UICOLOR_STANDARD_RED][0]                 = RGB(255,204,204);
		m_clrColors[UICOLOR_STANDARD_GREY][0]                = RGB(145,146,119);
		m_clrColors[UICOLOR_STANDARD_LIGHTGREY][0]           = RGB(195,196,179);
		m_clrColors[UICOLOR_STANDARD_WHITE][0]               = RGB(255,255,255);

		// Boot Windows Common Controls (for the ToolTip control)
		::InitCommonControls();

		// We need the image library for effects. It is however optional in Windows so
		// we'll also need to provide a gracefull fallback.
		::LoadLibrary("msimg32.dll");
	}
	m_szMinWindow.cx = 140;
	m_szMinWindow.cy = 200;
	m_ptLastMousePos.x = m_ptLastMousePos.y = -1;
	m_uMsgMouseWheel = ::RegisterWindowMessage(MSH_MOUSEWHEEL);
	// System Config
	m_SystemConfig.bShowKeyboardCues = false;
	m_SystemConfig.bScrollLists = false;
	// System Metrics
	m_SystemMetrics.cxvscroll = (INT) ::GetSystemMetrics(SM_CXVSCROLL);
}

CPaintManagerUI::~CPaintManagerUI() {
	// Delete the control-tree structures
	int i;
	for (i = 0; i < m_aDelayedCleanup.GetSize(); i++) {
		delete static_cast<CControlUI*>(m_aDelayedCleanup[i]);
	}
	delete m_pRoot;
	// Reset other parts...
	if (m_hwndTooltip != NULL) {
		::DestroyWindow(m_hwndTooltip);
	}
	if (m_hDcOffscreen != NULL)  {
		::DeleteDC(m_hDcOffscreen);
	}
	if (m_hbmpOffscreen != NULL)  {
		::DeleteObject(m_hbmpOffscreen);
	}
	if (m_hDcPaint != NULL)  {
		::ReleaseDC(m_hWndPaint, m_hDcPaint);
	}
	m_aPreMessages.Remove(m_aPreMessages.Find(this));
}

void CPaintManagerUI::Init(HWND hWnd) {
    ASSERT(::IsWindow(hWnd));
    // Remember the window context we came from
    m_hWndPaint = hWnd;
    m_hDcPaint = ::GetDC(hWnd);
    // We'll want to filter messages globally too
    m_aPreMessages.Add(this);
}

bool CPaintManagerUI::AttachDialog(CControlUI* pControl) {
	ASSERT(::IsWindow(m_hWndPaint));
	// Reset any previous attachment
	SetFocus(NULL);
	m_pEventKey = NULL;
	m_pEventHover = NULL;
	m_pEventClick = NULL;
	m_aNameHash.Empty();
	// Remove the existing control-tree. We might have gotten inside this function as
	// a result of an event fired or similar, so we cannot just delete the objects and
	// pull the internal memory of the calling code. We'll delay the cleanup.
	if ( m_pRoot != NULL ) {
		m_aDelayedCleanup.Add(m_pRoot);
		::PostMessage(m_hWndPaint, WM_APP + 1, 0, 0L);
	}
	// Set the dialog root element
	m_pRoot = pControl;
	// Go ahead...
	m_bResizeNeeded = true;
	m_bFocusNeeded = true;
	// Initiate all control
	return InitControls(pControl);
}

bool CPaintManagerUI::InitControls(CControlUI* pControl, CControlUI* pParent /*= NULL*/) {
	ASSERT(pControl);
	if (pControl == NULL) {
		return false;
	}
	pControl->SetManager(this, pParent != NULL ? pParent : pControl->GetParent());
	// We're usually initializing the control after adding some more of them to the tree,
	// and thus this would be a good time to request the name-map rebuilt.
	m_aNameHash.Empty();
	return true;
}

HINSTANCE CPaintManagerUI::GetResourceInstance() {
	return m_hInstance;
}

void CPaintManagerUI::SetResourceInstance(HINSTANCE hInst) {
	m_hInstance = hInst;
	if (m_hLangInst == NULL) {
		m_hLangInst = hInst;
	}
}

HWND CPaintManagerUI::GetPaintWindow() const {
	return m_hWndPaint;
}

HDC CPaintManagerUI::GetPaintDC() const {
    return m_hDcPaint;
}

POINT CPaintManagerUI::GetMousePos() const {
    return m_ptLastMousePos;
}

bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes) {
	// Not ready yet?
	if (m_hWndPaint == NULL) {
		return false;
	}
	switch ( uMsg ) {
	case WM_APP + 1: {
        // Delayed control-tree cleanup. See AttachDialog() for details.
        //for( int i = 0; i < m_aDelayedCleanup.GetSize(); i++ ) delete static_cast<CControlUI*>(m_aDelayedCleanup[i]);
        //m_aDelayedCleanup.Empty();
    }
    break;
	case WM_PAINT: {
        // Should we paint?
        RECT rcPaint = { 0 };
		if (!::GetUpdateRect(m_hWndPaint, &rcPaint, FALSE)) {
			return true;
		}
        // Do we need to resize anything?
        // This is the time where we layout the controls on the form.
        // We delay this even from the WM_SIZE messages since resizing can be a very expensize operation.
        if ( m_bResizeNeeded ) {
			RECT rcClient = { 0 };
			::GetClientRect(m_hWndPaint, &rcClient);
			if ( !::IsRectEmpty(&rcClient) ) { // rcClient为空 则IsRectEmpty返回true
				HDC hDC = ::CreateCompatibleDC(m_hDcPaint);
				m_pRoot->SetPos(rcClient);// 仅设置区域无效
				::DeleteDC(hDC);
				m_bResizeNeeded = false;
			}
            // Reset offscreen device
            if( m_hDcOffscreen != NULL ) ::DeleteDC(m_hDcOffscreen);
            if( m_hbmpOffscreen != NULL ) ::DeleteObject(m_hbmpOffscreen);
            m_hDcOffscreen = NULL;
            m_hbmpOffscreen = NULL;
        }

        // Render screen
        // Standard painting of control-tree - no 3D animation now.
        // Prepare offscreen bitmap?
        if ( m_bOffscreenPaint && m_hbmpOffscreen == NULL ) {
            RECT rcClient = { 0 };
            ::GetClientRect(m_hWndPaint, &rcClient);
            m_hDcOffscreen = ::CreateCompatibleDC(m_hDcPaint);
            m_hbmpOffscreen = ::CreateCompatibleBitmap(m_hDcPaint, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); 
            ASSERT(m_hDcOffscreen);
            ASSERT(m_hbmpOffscreen);
        }

        // Begin Windows paint
        PAINTSTRUCT ps = { 0 };
        ::BeginPaint(m_hWndPaint, &ps);
        if ( m_bOffscreenPaint ) {
            // We have an offscreen device to paint on for flickerfree display.
            HBITMAP hOldBitmap = (HBITMAP) ::SelectObject(m_hDcOffscreen, m_hbmpOffscreen);
            // Paint the image on the offscreen bitmap
            int iSaveDC = ::SaveDC(m_hDcOffscreen);

			// 开始绘制控件
			m_pRoot->DoPaint(m_hDcOffscreen, ps.rcPaint);

            ::RestoreDC(m_hDcOffscreen, iSaveDC);
            // Draw alpha bitmaps on top?
            m_aPostPaint.Empty();
            // Blit offscreen bitmap back to display
            ::BitBlt(ps.hdc, 
                ps.rcPaint.left, 
                ps.rcPaint.top, 
                ps.rcPaint.right - ps.rcPaint.left,
                ps.rcPaint.bottom - ps.rcPaint.top,
                m_hDcOffscreen,
                ps.rcPaint.left,
                ps.rcPaint.top,
                SRCCOPY);
            ::SelectObject(m_hDcOffscreen, hOldBitmap);
        }
        // All Done!
        ::EndPaint(m_hWndPaint, &ps);
	}
    // If any of the painting requested a resize again, we'll need
    // to invalidate the entire window once more.
    if (m_bResizeNeeded) {
        ::InvalidateRect(m_hWndPaint, NULL, FALSE);
    }
    return true;
    case WM_SIZE: {
        if ( m_pFocus != NULL ) {
            TEventUI event = { 0 };
            event.Type = UIEVENT_WINDOWSIZE;
            event.dwTimestamp = ::GetTickCount();
            m_pFocus->Event(event);
		}
		m_bResizeNeeded = true;
	}
	return true;
	case WM_LBUTTONDOWN: {
		// We alway set focus back to our app (this helps
		// when Win32 child windows are placed on the dialog
		// and we need to remove them on focus change).
		::SetFocus(m_hWndPaint);
		POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
		m_ptLastMousePos = pt;
		CControlUI* pControl = FindControl(pt);
		if (pControl == NULL) break;
		if (pControl->GetManager() != this) break;
		m_pEventClick = pControl;
		pControl->SetFocus();
		TEventUI event = { 0 };
		event.Type = UIEVENT_BUTTONDOWN;
		event.wParam = wParam;
		event.lParam = lParam;
		event.ptMouse = pt;
		event.wKeyState = wParam;
		event.dwTimestamp = ::GetTickCount();
		pControl->Event(event);
		// We always capture the mouse
		::SetCapture(m_hWndPaint);
	}
	break;
	case WM_LBUTTONUP: {
		POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
		m_ptLastMousePos = pt;
		if (m_pEventClick == NULL) break;
		::ReleaseCapture();
		TEventUI event = { 0 };
		event.Type = UIEVENT_BUTTONUP;
		event.wParam = wParam;
		event.lParam = lParam;
		event.ptMouse = pt;
		event.wKeyState = wParam;
		event.dwTimestamp = ::GetTickCount();
		m_pEventClick->Event(event);
		m_pEventClick = NULL;
	}
	break;
    case WM_CLOSE: {
		// Make sure all matching "closing" events are sent
	    TEventUI event = { 0 };
	    event.ptMouse = m_ptLastMousePos;
	    event.dwTimestamp = ::GetTickCount();
	    if (m_pEventHover != NULL) {
            event.Type = UIEVENT_MOUSELEAVE;
            event.pSender = m_pEventHover;
            m_pEventHover->Event(event);
	    }
	    if (m_pEventClick != NULL) {
            event.Type = UIEVENT_BUTTONUP;
            event.pSender = m_pEventClick;
            m_pEventClick->Event(event);
	    }
	    SetFocus(NULL);
	    // Hmmph, the usual Windows tricks to avoid
	    // focus loss...
	    HWND hwndParent = GetWindowOwner(m_hWndPaint);
	    if (hwndParent != NULL) ::SetFocus(hwndParent);
	}
	break;
    default:
		// Handle WM_MOUSEWHEEL
        if ( (uMsg == m_uMsgMouseWheel || uMsg == 0x020A) && m_pFocus != NULL ) {
            int zDelta = (int) (short) HIWORD(wParam);
            TEventUI event = { 0 };
            event.Type = UIEVENT_SCROLLWHEEL;
            event.wParam = MAKELPARAM(zDelta < 0 ? SB_LINEDOWN : SB_LINEUP, 0);
            event.lParam = lParam;
            event.dwTimestamp = ::GetTickCount();
            m_pFocus->Event(event);
            // Simulate regular scrolling by sending scroll events
            event.Type = UIEVENT_VSCROLL;
            for ( int i = 0; i < abs(zDelta); i += 40 ) {
                m_pFocus->Event(event);
            }
            // Let's make sure that the scroll item below the cursor is the same as before...
            ::SendMessage(m_hWndPaint, WM_MOUSEMOVE, 0, (LPARAM) MAKELPARAM(m_ptLastMousePos.x, m_ptLastMousePos.y));
        }
	break;
	}
	return false;
}

void CPaintManagerUI::UpdateLayout() {
    m_bResizeNeeded = true;
    ::InvalidateRect(m_hWndPaint, NULL, FALSE);
}

void CPaintManagerUI::Invalidate(RECT rcItem) {
	::InvalidateRect(m_hWndPaint, &rcItem, FALSE);
}

void CPaintManagerUI::ReapObjects(CControlUI* pControl) {
    if( pControl == m_pEventKey ) m_pEventKey = NULL;
    if( pControl == m_pEventHover ) m_pEventHover = NULL;
    if( pControl == m_pEventClick ) m_pEventClick = NULL;
    // TODO: Do something with name-hash-map
    // m_aNameHash.Empty();
}

void CPaintManagerUI::MessageLoop() {
    MSG msg = { 0 };
    while ( ::GetMessage(&msg, NULL, 0, 0) ) {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
}

CControlUI* CPaintManagerUI::GetFocus() const {
	return m_pFocus;
}

void CPaintManagerUI::SetFocus(CControlUI* pControl) {
	// Paint manager window has focus?
	if (::GetFocus() != m_hWndPaint) {
		::SetFocus(m_hWndPaint);
	}
	// Already has focus?
	if (pControl == m_pFocus) {
		return;
	}
	// Remove focus from old control
	if ( m_pFocus != NULL ) {
		TEventUI event = { 0 };
		event.Type = UIEVENT_KILLFOCUS;
		event.pSender = pControl;
		event.dwTimestamp = ::GetTickCount();
		m_pFocus->Event(event);
		m_pFocus = NULL;
	}
	// Set focus to new control
	if ( pControl != NULL
       && pControl->GetManager() == this
       && pControl->IsVisible()
       && pControl->IsEnabled() ) {
		m_pFocus = pControl;
		TEventUI event = { 0 };
		event.Type = UIEVENT_SETFOCUS;
		event.pSender = pControl;
		event.dwTimestamp = ::GetTickCount();
		m_pFocus->Event(event);
	}
}

TSystemSettingsUI CPaintManagerUI::GetSystemSettings() const {
	return m_SystemConfig;
}

TSystemMetricsUI CPaintManagerUI::GetSystemMetrics() const {
	return m_SystemMetrics;
}

HFONT CPaintManagerUI::GetThemeFont(UITYPE_FONT Index) const {
	if (Index <= UIFONT__FIRST || Index >= UIFONT__LAST) {
		return NULL;
	}
	if (m_hFonts[Index] == NULL) {
		m_hFonts[Index] = ::CreateFontIndirect(&m_aLogFonts[Index]);
	}
	return m_hFonts[Index];
}

const TEXTMETRIC& CPaintManagerUI::GetThemeFontInfo(UITYPE_FONT Index) const {
	if (Index <= UIFONT__FIRST || Index >= UIFONT__LAST) {
		return m_aTextMetrics[0];
	}
	if ( m_aTextMetrics[Index].tmHeight == 0 ) {
		HFONT hOldFont = (HFONT) ::SelectObject(m_hDcPaint, GetThemeFont(Index));
		::GetTextMetrics(m_hDcPaint, &m_aTextMetrics[Index]);
		::SelectObject(m_hDcPaint, hOldFont);
	}
	return m_aTextMetrics[Index];
}

COLORREF CPaintManagerUI::GetThemeColor(UITYPE_COLOR Index) const {
	if (Index <= UICOLOR__FIRST || Index >= UICOLOR__LAST){
		return RGB(0, 0, 0);
	}
	return m_clrColors[Index][0];
}

HPEN CPaintManagerUI::GetThemePen(UITYPE_COLOR Index) const {
	if (Index <= UICOLOR__FIRST || Index >= UICOLOR__LAST) return NULL;
	if (m_hPens[Index] == NULL) {
		m_hPens[Index] = ::CreatePen(PS_SOLID, 1, m_clrColors[Index][0]);
	}
	return m_hPens[Index];
}

bool CPaintManagerUI::GetThemeColorPair(UITYPE_COLOR Index, COLORREF& clr1, COLORREF& clr2) const {
	if (Index <= UICOLOR__FIRST || Index >= UICOLOR__LAST) return false;
	clr1 = m_clrColors[Index][0];
	clr2 = m_clrColors[Index][1];
	return true;
}

// CStartPageWnd 继承了 INotifyUI 类， CControlUI 也继承了 INotifyUI 类
// 这里参数传递的是 cstartPageWnd 类的实例
bool CPaintManagerUI::AddNotifier(INotifyUI* pNotifier) {
	ASSERT(m_aNotifiers.Find(pNotifier)<0);
	return m_aNotifiers.Add(pNotifier);
}

void CPaintManagerUI::SendNotify(CControlUI* pControl, LPCTSTR pstrMessage, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/) {
	TNotifyUI Msg;
	Msg.pSender = pControl;
	Msg.sType = pstrMessage;
	Msg.wParam = 0;
	Msg.lParam = 0;
	SendNotify(Msg);
}

void CPaintManagerUI::SendNotify(TNotifyUI& Msg) {
	// Pre-fill some standard members
	Msg.ptMouse = m_ptLastMousePos;
	Msg.dwTimestamp = ::GetTickCount();
	// Allow sender control to react
	Msg.pSender->Notify(Msg);
	// Send to all listeners 把消息发送给所有的窗口
	for (int i = 0; i < m_aNotifiers.GetSize(); i++) {
		static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
	}
}

CControlUI* CPaintManagerUI::FindControl(LPCTSTR pstrName) {
	ASSERT(m_pRoot);
	// First time here? Build hash array...
	if (m_aNameHash.GetSize() == 0) {
		int nCount = 0;
		m_pRoot->FindControl(__FindControlFromCount, &nCount, UIFIND_ALL);
		m_aNameHash.Resize(nCount + (nCount / 10));
		m_pRoot->FindControl(__FindControlFromNameHash, this, UIFIND_ALL);
	}
	// Find name in the hash array
	int nCount = 0;
	int nSize = m_aNameHash.GetSize();
	int iNameHash = (int)(GetNameHash(pstrName) % nSize);
	while (m_aNameHash[iNameHash] != NULL) {
		if (static_cast<CControlUI*>(m_aNameHash[iNameHash])->GetName() == pstrName) {
			return static_cast<CControlUI*>(m_aNameHash[iNameHash]);
		}
		iNameHash = (iNameHash + 1) % nSize;
		if (++nCount >= nSize) break;
	}
	return NULL;
}

CControlUI* CALLBACK CPaintManagerUI::__FindControlFromCount(CControlUI* /*pThis*/, LPVOID pData) {
	int* pnCount = static_cast<int*>(pData);
	(*pnCount)++;
	return NULL;  // Count all controls
}

CControlUI* CALLBACK CPaintManagerUI::__FindControlFromNameHash(CControlUI* pThis, LPVOID pData) {
	CPaintManagerUI* pManager = static_cast<CPaintManagerUI*>(pData);
	// No name?
	const CStdString& sName = pThis->GetName();
	if (sName.IsEmpty()) return NULL;
	// Add this control to the hash list
	int nCount = 0;
	int nSize = pManager->m_aNameHash.GetSize();
	int iNameHash = (int)(GetNameHash(sName) % nSize);
	while (pManager->m_aNameHash[iNameHash] != NULL) {
		iNameHash = (iNameHash + 1) % nSize;
		if (++nCount == nSize) return NULL;
	}
	pManager->m_aNameHash.SetAt(iNameHash, pThis);
	return NULL; // Attempt to add all controls
}

CControlUI* CPaintManagerUI::FindControl(POINT pt) const {
	ASSERT(m_pRoot);
	return m_pRoot->FindControl(__FindControlFromPoint, &pt, UIFIND_VISIBLE | UIFIND_HITTEST);
}

CControlUI* CALLBACK CPaintManagerUI::__FindControlFromPoint(CControlUI* pThis, LPVOID pData) {
	LPPOINT pPoint = static_cast<LPPOINT>(pData);
	return ::PtInRect(&pThis->GetPos(), *pPoint) ? pThis : NULL;
}

/////////////////////////////////////////////////////////////////////////////////////
//
CControlUI::CControlUI() : 
	m_pManager(NULL), 
	m_pParent(NULL), 
	m_pTag(NULL),
	m_bVisible(true), 
	m_bFocused(false),
	m_bEnabled(true) {
	::ZeroMemory(&m_rcItem, sizeof(RECT));
}

CControlUI::~CControlUI() {
	if (m_pManager != NULL) {
		m_pManager->ReapObjects(this);
	}
}

bool CControlUI::IsVisible() const {
	return m_bVisible;
}

bool CControlUI::IsEnabled() const {
	return m_bEnabled;
}

bool CControlUI::IsFocused() const {
	return m_bFocused;
}

UINT CControlUI::GetControlFlags() const {
	return 0;
}

bool CControlUI::Activate() {
	if (!IsVisible()) {
		return false;
	}
	if (!IsEnabled()) {
		return false;
	}
	return true;
}

CControlUI* CControlUI::GetParent() const {
	return m_pParent;
}

void CControlUI::SetFocus() {
	if (m_pManager != NULL) {
		m_pManager->SetFocus(this);
	}
}

CStdString CControlUI::GetText() const {
	return m_sText;
}

void CControlUI::SetText(LPCTSTR pstrText) {
	m_sText = pstrText;
	Invalidate();
}

void CControlUI::Init() {
}

CPaintManagerUI* CControlUI::GetManager() const {
	return m_pManager;
}

void CControlUI::SetManager(CPaintManagerUI* pManager, CControlUI* pParent) {
	bool bInit = m_pManager == NULL;
	m_pManager = pManager;
	m_pParent = pParent;
	if (bInit) {
		Init();
	}
}

CStdString CControlUI::GetName() const {
	return m_sName;
}

void CControlUI::SetName(LPCTSTR pstrName) {
	m_sName = pstrName;
}

LPVOID CControlUI::GetInterface(LPCTSTR pstrName) {
	if (_tcscmp(pstrName, _T("Control")) == 0) {
		return this;
	}
	return NULL;
}

CControlUI* CControlUI::FindControl(FINDCONTROLPROC Proc, LPVOID pData, UINT uFlags) {
	if ((uFlags & UIFIND_VISIBLE) != 0 && !IsVisible()) {
		return NULL;
	}
	if ((uFlags & UIFIND_ENABLED) != 0 && !IsEnabled()) {
		return NULL;
	}
	if ((uFlags & UIFIND_HITTEST) != 0 && !::PtInRect(&m_rcItem, *static_cast<LPPOINT>(pData))) {
		return NULL;
	}
	return Proc(this, pData);
}

RECT CControlUI::GetPos() const {
	return m_rcItem;
}

void CControlUI::SetPos(RECT rc) {
	m_rcItem = rc;
	// NOTE: SetPos() is usually called during the WM_PAINT cycle where all controls are
	//       being laid out. Calling UpdateLayout() again would be wrong. Refreshing the
	//       window won't hurt (if we're already inside WM_PAINT we'll just validate it out).
	Invalidate();
}

void CControlUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue) {
	if (_tcscmp(pstrName, _T("pos")) == 0) {
		RECT rcPos = { 0 };
		LPTSTR pstr = NULL;
		rcPos.left = _tcstol(pstrValue, &pstr, 10);  ASSERT(pstr);
		rcPos.top = _tcstol(pstr + 1, &pstr, 10);    ASSERT(pstr);
		rcPos.right = _tcstol(pstr + 1, &pstr, 10);  ASSERT(pstr);
		rcPos.bottom = _tcstol(pstr + 1, &pstr, 10); ASSERT(pstr);
		SetPos(rcPos);
	}
	else if (_tcscmp(pstrName, _T("name")) == 0) {
		SetName(pstrValue);
	}
	else if (_tcscmp(pstrName, _T("text")) == 0) {
		SetText(pstrValue);
	}
}

void CControlUI::Invalidate() {
	if (m_pManager != NULL) {
		m_pManager->Invalidate(m_rcItem);
	}
}

void CControlUI::UpdateLayout() {
	if (m_pManager != NULL) {
		m_pManager->UpdateLayout();
	}
}

void CControlUI::Event(TEventUI& event) {
	if ( event.Type == UIEVENT_SETCURSOR ) {
		::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
		return;
	}
	if ( event.Type == UIEVENT_SETFOCUS ) {
		m_bFocused = true;
		Invalidate();
		return;
	}
	if ( event.Type == UIEVENT_KILLFOCUS ) {
		m_bFocused = false;
		Invalidate();
		return;
	}
	if (m_pParent != NULL) {
		m_pParent->Event(event);
	}
}

void CControlUI::Notify(TNotifyUI& /*msg*/) {
}

CControlUI* CControlUI::ApplyAttributeList(LPCTSTR pstrList) {
	CStdString sItem;
	CStdString sValue;
	while( *pstrList != '\0' ) {
		sItem.Empty();
		sValue.Empty();
		while (*pstrList != '\0' && *pstrList != '=') {
			sItem += *pstrList++;
		}
		ASSERT(*pstrList=='=');
		if (*pstrList++ != '=') {
			return this;
		}
		ASSERT(*pstrList=='\"');
		if (*pstrList++ != '\"') {
			return this;
		}
		while (*pstrList != '\0' && *pstrList != '\"') {
			sValue += *pstrList++;
		}
		ASSERT(*pstrList=='\"');
		if (*pstrList++ != '\"') {
			return this;
		}
		SetAttribute(sItem, sValue);
		if (*pstrList++ != ',') {
			return this;
		}
	}
	return this;
}
